﻿/**
 * @file nohup.c
 * @author septem (jsm920@outlook.com)
 * @brief 像 linux 中的程序一下运行一个命令， 脱离终端， 即使终端关闭也不会停止程序运行
 * @version 0.1
 * @date 2024-11-21
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#pragma comment(linker, "/subsystem:windows")
#pragma comment(lib,"legacy_stdio_definitions.lib")
#define _CRT_NON_CONFORMING_WCSTOK 
#define _CRT_SECURE_NO_WARNINGS
#include <string.h>
#include <tchar.h>
#define WIN32_LEAN_AND_MEAN             // 从 Windows 头中排除极少使用的资料
#include <Windows.h>
#pragma comment(lib,"kernel32.lib")
#pragma comment(lib,"user32.lib")
#include <shlwapi.h>
#pragma comment(lib,"Shlwapi.lib")
#include <shellapi.h>
#pragma comment(lib,"shell32.lib")
#define STRSAFE_LIB
#include <strsafe.h>

void print_help()
{
    // nohup --help
    // Usage: nohup COMMAND [ARG]...
    //   or:  nohup OPTION
    // Run COMMAND, ignoring hangup signals.

    //       --help        display this help and exit
    //       --version     output version information and exit

    // If standard input is a terminal, redirect it from an unreadable file.
    // If standard output is a terminal, append output to 'nohup.out' if possible,
    // '$HOME/nohup.out' otherwise.
    // If standard error is a terminal, redirect it to standard output.
    // To save output to FILE, use 'nohup COMMAND > FILE'.

    // NOTE: your shell may have its own version of nohup, which usually supersedes
    // the version described here.  Please refer to your shell's documentation
    // for details about the options it supports.

    // GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
    // Full documentation <https://www.gnu.org/software/coreutils/nohup>
    // or available locally via: info '(coreutils) nohup invocation'
    MessageBox(GetParent(NULL),TEXT("Run a deamon exe file,like nginx.\
Usage: nohup COMMAND [ARG]...\n\
  or:  nohup OPTION\n\
Run COMMAND, ignoring hangup signals.\n\
\n\
      --help        display this help and exit\n\
      --version     output version information and exit\n"),
TEXT("nohup usage"), MB_OK | MB_ICONMASK);
}
void print_version()
{
    MessageBox(GetParent(NULL), TEXT("nohup version:0.01"),TEXT("version"), MB_OK);
}

LPCTSTR apszSuffix []= {TEXT(".exe"), TEXT("bat"),TEXT(".cmd"),TEXT(".vbs"),
                            TEXT(".vbe"),TEXT(".js"),TEXT(".jse"),TEXT(".wsf"),
                            TEXT(".wsh"),TEXT(".msc"),TEXT(".ps1"),TEXT(".com")
                            TEXT(".msi"),TEXT(".lnk"),};
/**
 * @brief 执行一个文件 
 * 
 * @param lpApplicationName 可执行文件的全路径，包括后缀
 * @param lpCommandLine  可执行文件的全路径，包括后缀 + 参数s
 * @return int 
 */
int execl(LPTSTR lpApplicationName,LPTSTR lpCommandLine )
{
    STARTUPINFOW StartupInfo = {0};
    PROCESS_INFORMATION ProcessInformation = {0};
    BOOL r = CreateProcess(lpApplicationName, 
                    lpCommandLine, 
                    NULL,
                    NULL,
                    FALSE,
                    0,
                    NULL,
                    NULL,
                    &StartupInfo,
                    &ProcessInformation);
    if(r == 0)
    {
        int err = GetLastError();
        if( (err == ERROR_FILE_NOT_FOUND || err == ERROR_ACCESS_DENIED)) //文件没有找到或者权限不足
        {
            return -2;
        }
        return -1;
    }
    return 0;
}
int execl_in_path(LPTSTR path,LPTSTR suffix, LPTSTR exeFile, LPTSTR exeFileFull, LPTSTR exeCommandline)
{
    PathCombine(exeFileFull, path, exeFile);
    if(suffix == NULL) //猜后缀名
    {
        for(int k = 0; k < sizeof(apszSuffix)/sizeof(apszSuffix[0]); k++)
        {
            _tcscat(exeFileFull, apszSuffix[k]);
            LPTSTR exeCommandline2 = exeFileFull + _tcslen(exeFileFull) + sizeof(TEXT('\0'));
            _tcscpy(exeCommandline2, exeFileFull);
            _tcscat(exeCommandline2, TEXT(" "));
            _tcscat(exeCommandline2, exeCommandline);
            switch(execl(exeFileFull, exeCommandline2))
            {
                case -1: // 严重错误，需要退出
                    return -1;
                case -2: //未找到文件或权限不足
                    break;
                case 0: // 执行成功，退出
                    return 0;
            }
        }
    }
    else
    {
        switch(execl(exeFileFull, exeCommandline))
        {
            case -1: // 严重错误，需要退出
                return -1;
            case -2: //未找到文件或权限不足
                return -2;
            case 0: // 执行成功，退出
                return 0;
        }
    }
    return -2;
}

int _tWinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPTSTR lpCmdLine,
    _In_ int nShowCmd
    )
{
    #define MAX_EXEENV_BUF 32767   //PATH 环境变量最大的 缓存数量
    LPTSTR exeFile = NULL;
    LPTSTR exeFileFull = NULL;
    LPTSTR exeCommandline = NULL; //指向 lpCmdLine 参数开始的部分,不可修改
    LPTSTR exeEnv = NULL;
    int cntPath = 0;
    LPTSTR paths[100] = {0};
    {
        size_t szCmdLine = lstrlen(lpCmdLine);
        exeFile = (LPTSTR)VirtualAlloc(NULL, 32767*2 + MAX_EXEENV_BUF, MEM_COMMIT, PAGE_READWRITE); //VirtualAlloc申请的内存为自动初始化为0
        if(exeFile == NULL)
        {
            MessageBox(GetParent(NULL), TEXT("ERROR:Can't alloc memory form system"), TEXT("ERROR"), MB_ICONERROR | MB_OK);
            return -1;
        }
        exeEnv = (LPTSTR )((char*)exeFile + 32767*2 + ((sizeof(void*)) - 32767*2 % (sizeof(void*))) % (sizeof(void*)));
        // 分析 lpCmdLine
        // 查找前面的空白字符
        int pos = 0;
        for( ; pos < szCmdLine; pos++)
        {
            if(!_istspace(lpCmdLine[pos]))
            {
                break;
            }
        }
        int pos_1 = pos; //保存这个命令开始位轩
        //检查空白字符后的这个字符是不是 ' " ` , 如果是 就向后查找 这个字符
        if(lpCmdLine[pos] == TEXT('"') || lpCmdLine[pos] == TEXT('\'') || lpCmdLine[pos] == TEXT('`') )
        {
            TCHAR ch = lpCmdLine[pos++];
            for( ; pos < szCmdLine; pos++)
            {
                if(lpCmdLine[pos-1] != TEXT('\\') && lpCmdLine[pos] == ch)
                {
                    pos++; //因为要包含 这个符号在 字符串中，所以加1
                    break;
                }
            }
        }
        else
        {
            for( ; pos < szCmdLine; pos++)
            {
                if(_istspace(lpCmdLine[pos]))
                {
                    break;
                }
            }
        }
        //把命令拷到 exeFile处
        _tcsncpy(exeFile, &lpCmdLine[pos_1], pos- pos_1);
        PathUnquoteSpaces(exeFile);
        PathRemoveBlanks(exeFile);
        exeFileFull = exeFile + pos- pos_1 + sizeof(TEXT('\0'));
        // 跳过程序与参数之间的空格
        for( ; pos < szCmdLine; pos++)
        {
            if(_istspace(lpCmdLine[pos]))
            {
                pos++;
                break;
            }
        }
        exeCommandline = &lpCmdLine[pos];
    }
    if(0 == _tcsicmp(exeFile, TEXT("--help")) || 0 == _tcsicmp(exeFile, TEXT("-h")))
    {
        print_help();
    } 
    else if(0 == _tcsicmp(exeFile, TEXT("--version")) || 0 == _tcsicmp(exeFile, TEXT("-v")))
    {
        print_version();
    } 
    else
    {
        if(PathFileExists(exeFile))
        {
            switch(execl(exeFile, exeCommandline))
            {
                case -1: // 严重错误，需要退出
                    break;
                case -2: //未找到文件或权限不足
                    break;
                case 0: // 执行成功，退出
                    break;
            }
        }
        else
        {
            #pragma warning(push)
            #pragma warning(disable:4090)
            LPTSTR suffix = PathFindSuffixArray(exeFile, apszSuffix, (int)(sizeof(apszSuffix)/sizeof(apszSuffix[0])));
            #pragma warning(pop)
            // 如果文件名不包含目录路径，系统将按以下顺序搜索可执行文件：
            // 从中加载应用程序的目录。
            // 父进程的当前目录。
            // 32 位 Windows 系统目录。 使用 GetSystemDirectory 函数获取此目录的路径。
            // 16 位 Windows 系统目录。 没有获取此目录路径的函数，但搜索该函数。 此目录的名称为 System。
            // Windows 目录。 使用 GetWindowsDirectory 函数获取此目录的路径。
            // PATH 环境变量中列出的目录。 请注意，此函数不会搜索 应用路径 注册表项指定的每个应用程序路径。 

            //1. 在当前目录中查找
            GetCurrentDirectory(32767,exeFileFull );
            switch(execl_in_path(exeFileFull,suffix,exeFile,exeFileFull,exeCommandline))
            {
                case -1: // 严重错误，需要退出
                    VirtualFree(exeFile,0,MEM_RELEASE);
                    return 0;
                    break;
                case -2: //未找到文件或权限不足
                    break;
                case 0: // 执行成功，退出
                    VirtualFree(exeFile,0,MEM_RELEASE);
                    return 0;
            }
            //2. 父进程的当前目录。//不处理

            //3.  32 位 Windows 系统目录。 使用 GetSystemDirectory 函数获取此目录的路径。
            GetSystemDirectory(exeFileFull,32767);
            switch(execl_in_path(exeFileFull,suffix,exeFile,exeFileFull,exeCommandline))
            {
                case -1: // 严重错误，需要退出
                    VirtualFree(exeFile,0,MEM_RELEASE);
                    return 0;
                    break;
                case -2: //未找到文件或权限不足
                    break;
                case 0: // 执行成功，退出
                    VirtualFree(exeFile,0,MEM_RELEASE);
                    return 0;
            }
            //4. Windows 目录。 使用 GetWindowsDirectory 函数获取此目录的路径。
            GetWindowsDirectory(exeFileFull,32767);
            switch(execl_in_path(exeFileFull,suffix,exeFile,exeFileFull,exeCommandline))
            {
                case -1: // 严重错误，需要退出
                    VirtualFree(exeFile,0,MEM_RELEASE);
                    return 0;
                    break;
                case -2: //未找到文件或权限不足
                    break;
                case 0: // 执行成功，退出
                    VirtualFree(exeFile,0,MEM_RELEASE);
                    return 0;
            }
            //5. PATH 环境变量中列出的目录
            DWORD  sz = GetEnvironmentVariable(TEXT("PATH"),exeEnv, MAX_EXEENV_BUF);
            if(sz == 0)
            {
                MessageBox(GetParent(NULL), TEXT("ERROR:Can't get PATH environment variable"), TEXT("ERROR"), MB_ICONERROR | MB_OK);
                VirtualFree(exeFile,0,MEM_RELEASE); //不用 return ，因为if else 成块的，会跳到尾部执行的
                return -1;
            }
            else
            {
                const LPTSTR _Delimiter = TEXT(";");
                LPTSTR token  = _tcstok(exeEnv, _Delimiter);
                while( token != NULL && cntPath < sizeof(paths)/sizeof(paths[0]))
                {
                    switch(execl_in_path(token,suffix,exeFile,exeFileFull,exeCommandline))
                    {
                        case -1: // 严重错误，需要退出
                            VirtualFree(exeFile,0,MEM_RELEASE);
                            return 0;
                            break;
                        case -2: //未找到文件或权限不足
                            break;
                        case 0: // 执行成功，退出
                            VirtualFree(exeFile,0,MEM_RELEASE);
                            return 0;
                    }

                    token = _tcstok( NULL, _Delimiter ); // C4996
                    cntPath++;
                }
            }
        }
    }
    VirtualFree(exeFile,0,MEM_RELEASE);
    return 0;
    //分析 lpCmdLine 去掉 前面的

    // LPTSTR  _lpCmdLine = GetCommandLineW();
    // int argc = 0;
    // LPTSTR  argv = GetCommandLineW();
    


    // LPWSTR * argv = CommandLineToArgvW(_lpCmdLine, &argc);
    // if(argc <= 1 || 0 == lstrcmpi(argv[1], TEXT("--help")) || 0 == lstrcmpi(argv[1], TEXT("-h")))
    // {
    //     print_help();
    //     LocalFree (argv);
    //     return 0;
    // }
    // if( 0 == lstrcmpi(argv[1], TEXT("--version")) || 0 == lstrcmpi(argv[1], TEXT("-v")))
    // {
    //     print_version();
    //     LocalFree (argv);
    //     return 0;
    // }
    // LPTSTR exeFile = NULL;
    // LPTSTR exeCommandline = NULL;
    // LPTSTR exeEnv = NULL;
    // int cntPath = 0;
    // LPTSTR paths[100] = {0};
    // // 处理command line for CreateProcess second param
    // {
    //     // size_t len_cmd = lstrlen(_lpCmdLine);
    //     size_t len = 0;
    //     for(int i = 1; i < argc; i++)
    //     {
    //         len+=lstrlen(argv[i]);
    //         len += (sizeof(TEXT(' ')) + sizeof(TEXT('\0')));
    //     }
    //     exeFile = (LPWSTR)VirtualAlloc(NULL,len  + 120*30, MEM_COMMIT,PAGE_READWRITE); //内存为自动初始化为0
    //     if(exeFile == NULL)
    //     {
    //         MessageBox(GetParent(NULL), TEXT("ERROR:Can't alloc memory form system"), TEXT("ERROR"), MB_ICONERROR | MB_OK);
    //         LocalFree (argv);
    //         return -1;
    //     }
    //     lstrcpy(exeFile, argv[1]);
    //     exeCommandline = exeFile + lstrlen(exeFile) + sizeof(TEXT('\0'));
    //     for(int i = 2; i < argc; i++)
    //     {
    //         lstrcat (exeCommandline, argv[i]);
    //         lstrcat (exeCommandline, TEXT(" "));
    //     }
    //     if( 0 == lstrlen(exeCommandline))
    //     {
    //         exeCommandline = NULL;
    //     }
    //     exeEnv = exeCommandline+ lstrlen(exeCommandline) + sizeof(TEXT('\0')) ;
    //     LocalFree (argv);
    // }

    // //获取第二个参数，作为程序文件，然后在环境变量按顺序查找
    // if(PathFileExists(exeFile))
    // {
    //     execl(exeFile,exeCommandline, NULL);
    // }
    // else
    // {

    //     DWORD  sz = GetEnvironmentVariable(TEXT("PATH"),exeEnv, 120*30);
    //     if(sz == 0)
    //     {
    //         MessageBox(GetParent(NULL), TEXT("ERROR:Can't get PATH environment variable"), TEXT("ERROR"), MB_ICONERROR | MB_OK);
    //         VirtualFree(exeFile,0,MEM_RELEASE);
    //         return -1;
    //     }
    //     // 把 %PATH% 环境变量分成 字符串数组
        
    //     #ifdef UNICODE
    //     #define lstrtok wcstok  
    //     #else
    //     #define lstrtok strtok  
    //     #endif // !UNICODE
    //     const LPTSTR _Delimiter = TEXT(";");
    //     LPTSTR token  = lstrtok(exeEnv, _Delimiter);
    //     while( token != NULL && cntPath < sizeof(paths)/sizeof(paths[0]))
    //     {
    //         paths[cntPath] = token;
    //         token = lstrtok( NULL, _Delimiter ); // C4996
    //         cntPath++;
    //     }
    // }
    // for(int i = 0; i != cntPath; i++)
    // {
    //     int r = execl(exeFile, exeCommandline, paths[i]);
    //     if(r >0)
    //     {
            
    //         break;
    //     }
    //     else
    //     {
    //         int x = GetLastError();
    //         x = GetLastError();
    //     }
    // }
    // VirtualFree(exeFile,0,MEM_RELEASE);

    // LPWSTR argv2 = NULL; 
    // char* argv3 = NULL;
    // {
    //     //处理 _lpCmdLine，去掉前面的hide.exe
    //     size_t sz = 0;
    //     StringCchLengthW(_lpCmdLine,STRSAFE_MAX_CCH,&sz);
    //     size_t pos = 0;
    //     for(size_t i = 0; i < sz; i++)
    //     {
    //         if(_lpCmdLine[i] == L' ' || _lpCmdLine[i] == L'\t')
    //         {
    //             pos = ++i;
    //             break;
    //         }
    //     }
    //     argv2 = (LPWSTR)LocalAlloc(LMEM_ZEROINIT,  (sz )* sizeof(WCHAR ) + sizeof(void*)); 
    //     StringCchCopyNW(argv2,sz, &_lpCmdLine[pos],sz - pos+1);

    //     argv3 = (char*)LocalAlloc(LMEM_ZEROINIT,  (sz )* sizeof(WCHAR ) *3);
    //     size_t  sz2 = 0;
    //     StringCchLengthW(_lpCmdLine,STRSAFE_MAX_CCH,& sz2);
    //     WideCharToMultiByte(
    //         CP_ACP,
    //         0,
    //         argv2,
    //         (int)(sz2),
    //         argv3,
    //         (int)((sz )* sizeof(WCHAR ) *3), NULL,FALSE
    //     );
    // }

    // UINT r= WinExec(argv3,SW_SHOWNORMAL);
    // if(r < 31)
    // {
    //     LPCTSTR lpMsgBuf = (r == 0 ? L"The system is out of memory or resource" :(r == ERROR_BAD_FORMAT ? 
    //                                 L"The .exe file is invalid":(r==ERROR_FILE_NOT_FOUND?
    //                                 L"The specified file was not found":(r==ERROR_PATH_NOT_FOUND?
    //                                 L"The specified path was not found":L"Unknown error"))));
    //     LPVOID lpDisplayBuf;
    //     DWORD dw = GetLastError(); 

    //     lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
    //         (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)argv2)+80)*sizeof(TCHAR)); 
    //     StringCchPrintfW ((LPTSTR)lpDisplayBuf, 
    //         LocalSize(lpDisplayBuf),
    //         TEXT("Error: CreateProcess,errno:%d:%s.\nCommand:%s"), 
    //         r, lpMsgBuf, argv2); 
    //     MessageBoxExW(NULL, lpDisplayBuf, L"hide Create Process failed",MB_ICONERROR|MB_OK,0);
    //     LocalFree(lpDisplayBuf);
    // }

    // // STARTUPINFOW StartupInfo = {0};
    // // PROCESS_INFORMATION ProcessInformation = {0};
    // // // MessageBoxExW(NULL, argv[1], argv2,MB_OK,0);
    // // BOOL r = CreateProcessW(argv[1],argv2,NULL,NULL,FALSE,0,NULL,NULL,&StartupInfo,&ProcessInformation);
    // // if(r == 0)
    // // {
    // //     // Retrieve the system error message for the last-error code
    
    // //     LPVOID lpMsgBuf;
    // //     LPVOID lpDisplayBuf;
    // //     DWORD dw = GetLastError(); 
    // //     FormatMessage(
    // //         FORMAT_MESSAGE_ALLOCATE_BUFFER | 
    // //         FORMAT_MESSAGE_FROM_SYSTEM |
    // //         FORMAT_MESSAGE_IGNORE_INSERTS,
    // //         NULL,
    // //         dw,
    // //         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    // //         (LPTSTR) &lpMsgBuf,
    // //         0, NULL );
    
    // //     // Display the error message and exit the process
    
    // //     lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
    // //         (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)argv2)+80)*sizeof(TCHAR)); 
    // //     StringCchPrintfW ((LPTSTR)lpDisplayBuf, 
    // //         LocalSize(lpDisplayBuf),
    // //         TEXT("Error: CreateProcess,errno:%d:%s.\nCommand:%s"), 
    // //         dw, lpMsgBuf, argv2); 
    // //     MessageBoxExW(NULL, lpDisplayBuf, L"hide Create Process failed",MB_ICONERROR|MB_OK,0);
    // //     LocalFree(argv2);
    // //     LocalFree(lpMsgBuf);
    // //     LocalFree(lpDisplayBuf);
    // //     return r;
    // // }
    // LocalFree(argv2);
    // LocalFree(argv3);
    return 0;
}